package edu.sjtu.context.core;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

import org.apache.commons.logging.*;

import com.hp.hpl.jena.util.FileUtils;

import edu.sjtu.context.persist.*;
import edu.sjtu.context.prob.LocalContextProb;
import edu.sjtu.context.prob.RemoteContextProb;
import edu.stanford.smi.protegex.owl.ProtegeOWL;
import edu.stanford.smi.protegex.owl.jena.JenaOWLModel;
import edu.stanford.smi.protegex.owl.model.OWLOntology;
import edu.stanford.smi.protegex.owl.repository.RepositoryManager;
import edu.stanford.smi.protegex.owl.repository.impl.LocalFileRepository;

public class ContextManager {
	/**
	 * single instance of ContextManager
	 */
	private static ContextManager cm = null;
	/**
	 * log instance
	 */
	private static Log log = null;
	/**
	 * configuration properties
	 */
	private static Properties props = null;
	
	/**
	 * An ontology to descibe local context infomation
	 */
	private static JenaOWLModel localOntology;

	/**
	 * persist manager
	 */
	private static PersistManager pm = null;
	
	/**
	 * topic manager
	 */
	private static TopicManager tm = null;
	/**
	 * static inner class to initialize the ContextManager
	 * @author Roger
	 *
	 */
	static class ContextManagerHolder{
		static volatile ContextManager cm =new ContextManager();
	}
	
	/**
	 * Constructor of ContextManager
	 * 
	 * @author Roger
	 */
	private ContextManager() {
		log = LogFactory.getLog(this.getClass());

		try {
			props = this.getProperties();
			for (java.util.Iterator it = props.keySet().iterator(); it
					.hasNext();) {
				String key = it.next().toString();
				log.info(key + ":" + props.getProperty(key));
			}
			
			//create the local context ontology
			localOntology = this.createLocalContextOntology();
			
			if(localOntology == null){
				log.error("Create local context ontology failed!");
			}
			else{
				log.info("Create local context ontology successful. The ontology name is:localont!");
			}
			
			//initialize a ontology persist manager base on the configuration file.If the class indicated in the 
			//configuration file exist and it is initialized successfully, the user can use getPersistManager method
			//to get the instance, or this method will return null.
			Class cl = Thread.currentThread().getContextClassLoader().loadClass(props.getProperty("persist.manager"));
			Object obj = cl.newInstance();
			if(obj instanceof PersistManager){
				pm = (PersistManager)obj;
				pm.setOntLibPath(this.props.getProperty("ont.lib.path"));
			}
			//end
			
			//initialize a topic manager base on the configuration file.If the class indicated in the 
			//configuration file exist and it is initialized successfully, the user can use getTopicManager method
			//to get the instance, or this method will return null.
			cl = Thread.currentThread().getContextClassLoader().loadClass(props.getProperty("topic.manager"));
			obj = cl.newInstance();
			if(obj instanceof TopicManager){
				tm = (TopicManager)obj;
				//String url = props.getProperty("connURL");
				tm.getConnection(props.getProperty("connURL"));
				String[] topics = props.getProperty("tipics").split(",");
				tm.createTopics(topics);
			}
			//end
			
			//start all probes specified in the configuration file
			this.startProbs();
		} catch (java.io.FileNotFoundException e) {
			log.error("ontmanager.properitites not found. Please set it properly.");
			throw new RuntimeException(e.getMessage());
		} catch (java.io.IOException e) {
			log.error("Load ontmanager.propertities failed. Please check the access privilige.");
			throw new RuntimeException(e.getMessage());
		} catch (ClassNotFoundException e){
			throw new RuntimeException(e.getMessage());
		} catch (Exception e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * return the single instance of ContextManager
	 * 
	 * @return the single instance of ContextManager
	 * @author Roger
	 */
	public static ContextManager getInstance() {
		return ContextManagerHolder.cm;
	}

	/**
	 * get the persist manager
	 * @return the proper persist manager
	 * @author Roger
	 */
	public PersistManager getPersistManager(){
		/*
		Class cl = Thread.currentThread().getContextClassLoader().loadClass(this.props.getProperty("persist.manager"));
		Method method = cl.getDeclaredMethod("getInstance", new Class[]{});
		Object obj = method.invoke(cl, new Object[]{});
		
		if(obj instanceof PersistManager){
			PersistManager pm = (PersistManager)obj;
			pm.setOntLibPath(this.props.getProperty("ont.lib.path"));
			return (PersistManager)obj;
		}
		
		return null;
		*/
		return pm;
	}

	/**
	 * 
	 * @return
	 */
	public TopicManager getTopicManager(){
		return tm;
	}
	/**
	 * get configuration properties
	 * 
	 * @return configuration properties
	 * @throws java.io.FileNotFoundException
	 *             if the properties file is not exist, throw a exception.
	 * @throws java.io.IOException
	 *             if load properties file failed, throw a exception.
	 *             
	 * @author Roger
	 */
	private Properties getProperties() throws java.io.FileNotFoundException,
			java.io.IOException {
		// get the configuration file. Configure the ContextManager based on the
		// config file
		// if it not exist, throw a exception
		URL path = Thread.currentThread().getContextClassLoader().getResource("ontmanager.propertities");
		Properties props = new Properties();
		FileInputStream propFile = new FileInputStream(path.getFile());
		props.load(propFile);
		return props;
	}
	
	/**
	 * Start all the probes declared in the configuration file.
	 * 
	 * @author Roger
	 */
	private void startProbs(){
		String tempProbs = this.props.getProperty("local.probs");
		String frequency = this.props.getProperty("frequency");
		
		if(tempProbs == null){
			log.info("no probes!");
			return;
		}
		String[] probs = tempProbs.split(",");
		try{
			for(String prob : probs){
				log.info("To start probe wiht name:" + prob);
				Class cl = Thread.currentThread().getContextClassLoader().loadClass(prob);
				Object obj = cl.newInstance();
				if(obj instanceof LocalContextProb){
					LocalContextProb localProb = (LocalContextProb)obj;
					if(frequency != null)
						localProb.setFrequency(Integer.parseInt(frequency));
					localProb.addLocalContextChangeListener(ContextGauge.getInstance());
					new Thread(localProb).start();
				}
//				else if(obj instanceof RemoteContextProb){
//					RemoteContextProb remoteProb = (RemoteContextProb)obj;
//					if(frequency != null)
//					remoteProb.addRemoteContextChangeListener(ContextGauge.getInstance());
//					new Thread(remoteProb).start();
//				}
			}
		}catch(ClassNotFoundException e){
			log.error(e.getMessage());
		}catch(Exception e){
			log.error(e);
		}
	}
	
	/**
	 * Create an default ontology to describe local context.
	 * @return
	 * 		return the created local context ontology. If there are problem during the creation process,
	 * 		this method return null. 
	 * @author Roger
	 */
	private JenaOWLModel createLocalContextOntology(){
		JenaOWLModel model;
		JenaOWLModel intModel;
		Collection errors = new ArrayList();
		
		try{
			model = ProtegeOWL.createJenaOWLModel();
			intModel = ProtegeOWL.createJenaOWLModelFromInputStream(new FileInputStream(new File(
					Thread.currentThread().getContextClassLoader().getResource("InternetContext.owl").getFile())));
			RepositoryManager rm = model.getRepositoryManager();
			//add internet ontology to created ontology's repository
		    LocalFileRepository lfRep = new LocalFileRepository(new File(Thread.
		    		currentThread().getContextClassLoader().getResource("InternetContext.owl").getFile()), false);
		    rm.addProjectRepository(lfRep);
		    
		    //add the uri of internet ontology to created imports declaration
		    OWLOntology ont = model.getDefaultOWLOntology();
		    ont.addImports(intModel.getDefaultOWLOntology());
		    
		    //set default namespace of the created ontology
		    model.getNamespaceManager().setDefaultNamespace("http://www.cs.sjtu.edu.cn/localont#");
		    //add reference of internet ontology to created ontology's namespace declaration
		    model.getNamespaceManager().setPrefix(intModel.getDefaultOWLOntology().getURI()+"#", "intont");
		    model.save(new File(this.props.getProperty("ont.lib.path") + "\\localont.owl").toURI(), 
		    		FileUtils.langXMLAbbrev, errors);
		    
		    return ProtegeOWL.createJenaOWLModelFromURI("file:/" + this.props.getProperty("ont.lib.path") + "/" + "localont.owl");
		}
		catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * 
	 */
	public synchronized void updateLocalContextOntology(){
		Collection errors = new ArrayList();
		this.localOntology.save(new File(this.props.getProperty("ont.lib.path") + "\\localont.owl").toURI(), 
	    		FileUtils.langXMLAbbrev, errors, this.localOntology.getOntModel());
		log.info("Save local context ontology with (" + errors.size() + ") error/errors.");
	}
	
	/**
	 * get local context ontology
	 * @return
	 * @author Roger
	 */
	public JenaOWLModel getLocalOntology() {
		return localOntology;
	}
	
	/**
	 * start remote prob
	 * 
	 * @param connUrl
	 * 		the connection string used to connect remote message broker.
	 * @param topic
	 * 		the topic to subscribe.
	 * @param ontName
	 * 		the ontology which the detected context will be send to.
	 * @author Roger
	 */
	public void setupRemoteProb(String connUrl, String topic, String ontName){
		try{
			Class cl = Thread.currentThread().getContextClassLoader().loadClass(props.getProperty("remote.prob"));
			Object obj = cl.newInstance();
			if(obj instanceof RemoteContextProb){
				RemoteContextProb prob = (RemoteContextProb)obj;
				prob.setOntName(ontName);
				prob.setSubscribeTopic(topic);
				prob.setConnUrl(connUrl);
				prob.addRemoteContextChangeListener(ContextGauge.getInstance());
				new Thread(prob).start();
			}
		} catch(Exception e){
			e.printStackTrace();
		}
	}
}
